#======================================================================
#  EntityChooser.py
#  KnowledgeEngine
#  
#  Created by John Meredith on 2006-12-19.
#  Copyright 2006 Faulkner Technologies. All rights reserved.
#======================================================================


#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Component import Component, ComponentInfo
from Products.KnowledgeEngine.Assessible import Assessible
from Products.KnowledgeEngine.AssessmentFilter import AssessmentFilter
from Products.KnowledgeEngine.AssessmentComponentState import AssessmentComponentState
from Products.KnowledgeEngine.AssessmentComponentValueTypes import *
from Products.KnowledgeEngine.Exportable import Exportable
from Products.KnowledgeEngine.KERFProcessor import resolveKERF
from Products.KnowledgeEngine.KERLProcessor import *
from Products.KnowledgeEngine.KERLExtensions import KERLExtensions
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.Graphing.Graphable import *
from Products.KnowledgeEngine.Graphing.Datum import *
from Products.KnowledgeEngine.User import User 
from Products.KnowledgeEngine.Caching import RequestCache 
from Products.KnowledgeEngine.SimpleWizard import *
from Products.KnowledgeEngine.WizardStepDefinition import *
from Products.KnowledgeEngine.WizardState import *


#####################################################################
## Zope CMF Library Imports
from Products.CMFCore.PortalContent import PortalContent
from Products.CMFCore.ActionInformation import ActionInformation
from Products.CMFCore import permissions
from Products.CMFCore.permissions import ModifyPortalContent
from Products.CMFCore.utils import getToolByName


#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from App.ImageFile import ImageFile


#####################################################################
## Python Library Imports
# (none)
import types

#======================================================================
# Return all EntitySources defined within the Plone portal
#----------------------------------------------------------------------
def getEntitySources(context):
    """
        Return the entity sources in the portal as [(Title,Identity)]
    """
    
    #get the portal catalog
    catalog = getToolByName(context, "knowledgeengine_genericobjectcatalogservice")
    
    #search for entity sources by their family
    searchResults = catalog(getClassFamilyName = "EntitySource")
    
    #results aren't the actual objects, so lets reference them
    result = []
    for searchResult in searchResults:
        #attempt to resolve the actual object reference
        try:
            entitySource = searchResult.getObject()
        except AttributeError: 
            continue # Skip 'broken' zope objects
        
        #only add models that truely exist!
        if entitySource is not None:
            result = result + [(entitySource.title_or_id(), entitySource.getIdentity())]
            
    return result

#======================================================================
# EntityChooser ClassInfo
#----------------------------------------------------------------------
class EntityChooserInfo(ComponentInfo, Assessible):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """


    #---------------------------------------------------------------#
    def getClassName(self):
        """
            Return the class name
        """
        return 'EntityChooser'


    #---------------------------------------------------------------#
    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Entity Chooser"

        
    #---------------------------------------------------------------#
    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "ec"

        
    #---------------------------------------------------------------#
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/EntityChooser_icon.gif', globals())

        
    #---------------------------------------------------------------#
    def isContainer(self):
        """
            Returns if the instance is providing folderish/container
            services.
            
            Override an return true if the instance is to be
            a container for other instances.
            
            NOTE: all instances support "events" so there is no
            need to return true if you require "event" support.
        """

        return false          

        
    #---------------------------------------------------------------#
    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified
            by the className
        """

        return className in ('Assessible', 'Linkable', 'Exportable') or ComponentInfo.isInstanceOf(self, className)

        
    #---------------------------------------------------------------#
    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a 
            child of the class' instances.
            
            NOTE: This is from this class' instance perspective.
            
            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """
        
        #DEFAULT RULE: nothing is permitted in an entity chooser
        return false

        
    #---------------------------------------------------------------#
    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in 
            the specified container (containerInstance)
            
            NOTE: This is from this class' instance perspective.
        """
        
        #DEFAULT RULE: entity choosers are only permitted if there are some entity sources
        return len(getEntitySources(containerInstance or self)) > 0 and ComponentInfo.isPermittedInContainer(self, containerClassInfo, containerInstance, instance)

        
    #---------------------------------------------------------------#
    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        ComponentInfo.onConfigureProperties(self)          

        #add the 'entitySourceIdentity' property        
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="entitySourceIdentity",
                displayName="Entity Source",
                description="The source from which Entities will be chosen",
                propertyType=pt_STRING,
                propertyFormat=pf_NATIVE,
                hasEnumeratedValues=true,
                enumeratedValuesFunction=getEntitySources,
                defaultValue=None,
                constructorEVAP=evap_READWRITE,
                #spropertyEditorEVAP=evap_READONLY,
                isMandatory=true
            )
        )

        # add the 'AssessmentTitleTemplate' property
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="entityTitleTemplate",
                displayName="Entity Title Template",
                description="A KERF or 'script:' statement defining entity names (Leave blank to default to EntitySource default)",
                defaultValue="",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_MULTILINE,
                propertyStructure=ps_ATOMIC
            )
        )

        # Entity display template. Overrides entity source template.
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="entityDisplayTemplate",
                displayName="Entity Display Template",
                description="The name of the template to be used to display the entity in assessments and reports. Leave empty to use the template defined in the entity source.",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_SINGLELINE,
                defaultValue=None,
                constructorEVAP=evap_READWRITE,
                propertyEditorEVAP=evap_READWRITE,
            )
        )

        # Is this component multi-valued?
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="multivalued",
                displayName="Multi-Valued",
                description="Can this component store multiple-values?",
                defaultValue=false,
                propertyType=pt_BOOLEAN,
                propertyFormat=pf_CHECKBOX
            )
        )

        # Add simple filtering mechanism
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="filterComponentIdentity",
                displayName="Filter Component Identity (ALPHA)",
                description="The identity of the component whose value will be used to filter the entity source results",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_SINGLELINE,
                defaultValue="",
            )
        )

        self.addPropertyDefinition(
            PropertyDefinition(
                identity="filterPropertyName",
                displayName="Filter Property name (ALPHA)",
                description="The name of the property on an entity which will be filtered against. Note: Case sensitive!",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_SINGLELINE,
                defaultValue="",
            )
        )

        # A property to allow selection of where the component
        # will display itself in assessments i.e. inline, popup
        # or wiardz
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="displayMethod",
                displayName="Display Method",
                description="Where this component will display itself in an assessment",
                propertyType=pt_STRING,
                propertyFormat=pf_NATIVE,
                defaultValue="Inline",
                hasEnumeratedValues=true,
                enumeratedValuesList=['Inline', 'Wizard', 'Popup'],
                constructorEVAP=evap_READWRITE,
                propertyEditorEVAP=evap_READWRITE,
                isMandatory=true
            )
        )

        # This property determines how the component will be
        # rendered in the assessment i.e. what type of widget will
        # be used to display it
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="displayFormat",
                displayName="Display As",
                description="How options are to be selected/displayed? [NOTE/TODO: These options are ignored if Wizard mode selected]",
                propertyType=pt_STRING,
                propertyFormat=pf_NATIVE,
                defaultValue="Checkbox List",
                hasEnumeratedValues=true,
                enumeratedValuesList=['2 Column Selector', 'Checkbox/Radiobox List', 'Select List', 'Drop Down'],
                constructorEVAP=evap_READWRITE,
                propertyEditorEVAP=evap_READWRITE,
                isMandatory=true
            )
        )

        self.addPropertyDefinition(
            PropertyDefinition(
                identity="enableFiltering",
                displayName="Enable filtering",
                description="Enable/disable whether the filter box will be displayed on this component or not",
                defaultValue=false,
                propertyType=pt_BOOLEAN,
                propertyFormat=pf_CHECKBOX
            )
        )
        
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="enableSorting",
                displayName="Enable sorting",
                description="Enable/disable whether this entity chooser will sort",
                defaultValue=false,
                propertyType=pt_BOOLEAN,
                propertyFormat=pf_CHECKBOX
            )
        )


#======================================================================
# EntityChooser definition
#----------------------------------------------------------------------
class EntityChooser(Component, Assessible, KERLExtensions, Exportable):
    """
        The Entity Chooser Component
    """

    #---------------------------------------------------------------#
    #TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = EntityChooserInfo()


    #---------------------------------------------------------------#
    def __init__(self, id, identity):
        """
            Constructor for the EntityChooser
        """

        Component.__init__(self, id, identity)



    def isEntity(self, object):
        """ 
            Utility: Queries an object to determine if it's an entity or not
        """
        
        return hasattr(object, 'getIdentity')
        

    #---------------------------------------------------------------#
    
    def getEnableSorting( self ):
        """ Should this entity chooser be sorted? """
        
        return getattr(self.aq_explicit, 'enableSorting', False)
    
    
    
    #---------------------------------------------------------------#
    
    def getComponentProperty(self, identity, propertyName, assessment = None):
        """
            Returns the value of the specified property, given the
            identity of the component (which may be used to lookup states) and
            an assessment.

            RETURNS: None if the property doesn't exist
        """

        if propertyName == "value":
            return self.getEntity( assessment.getComponentState(identity).getValue() ).getTitle()
        else:
            return Component.getComponentProperty(self, identity, propertyName, assessment)


    #---------------------------------------------------------------#
    def getMaximumEntitiesForSimpleChooser(self):
        """
            The maximum number of entities to appear in a dropdown
            otherwise a finder is required.
        """
        
        return 100


    #---------------------------------------------------------------#
    def getEntitySource(self):
        """
            Return the entity source that provides entities 
            for this chooser.
            
            Returns None if the entity source is no longer available
        """
        
        #get the portal catalog
        catalog = getToolByName(self, "knowledgeengine_genericobjectcatalogservice")
        
        #search for the entity source identity
        searchResults = catalog(getIdentity = self.entitySourceIdentity)
        
        if len(searchResults) == 1:
            return searchResults[0].getObject()
        else:
            return None


    #---------------------------------------------------------------#
    
    def getEntitySourceIdentity(self):
        """ Returns the entity source identity """
        
        return getattr(self.aq_explicit, 'entitySourceIdentity')
        
    #---------------------------------------------------------------#
            
    def setEntitySourceIdentity(self, value):
        """ Sets the entity source identity """
        
        self.entitySourceIdentity = value
    
    #---------------------------------------------------------------#        
        
        
    def getEntityTitleTemplate(self):
        """
            Returns the string which represents the displayed title
            of the entity in question
        """
        
        if hasattr(self, 'entityTitleTemplate'):
            return self.entityTitleTemplate
        
        return None


    #---------------------------------------------------------------#
    def getEntityDisplayTemplate(self):
        """
            Returns the template used to display this entity. Checks
            locally first, before delegating to the EntitySource.
        """
        
        if self.entityDisplayTemplate:
            return self.entityDisplayTemplate
        
        return self.getEntitySource().getEntityDisplayTemplate()

        
    #---------------------------------------------------------------#
    def renderEntity(self, request, **args):
        """
            Return the rendered entity
        """
        
        #dynamically determine the assessment editor rendering page for this object
        return eval("self.%s(self, REQUEST=request, **args)" % self.getEntityDisplayTemplate())


    #---------------------------------------------------------------#
    def getFilterComponentValue(self, assessment):
        """
            Return the value of the component which will be used to
            filter against
        """

        identity = self.getFilterComponentIdentity()
        if identity:
            return assessment.getComponentState(identity).getValue()
            
        return None


    #---------------------------------------------------------------#
    def getFilterComponentIdentity(self):
        """ Returns the identity of the component who's value will be used to filter """
        
        return self.filterComponentIdentity or None


    #---------------------------------------------------------------#
    def getFilter(self, assessment):
        """
            Construct the filter to be used against the entity source
        """

        result = {}
        if self.getFilterComponentIdentity() and self.getFilterPropertyName():
            result = { self.getFilterPropertyName() : self.getFilterComponentValue(assessment) }
        
        return result


    #---------------------------------------------------------------#
    def getFilterPropertyName(self):
        """ Returns the name of the entity property to filter against """
        
        return self.filterPropertyName or None


    #---------------------------------------------------------------#
    def getDisplayMethod(self):
        """
            Returns where the component will display itself (at this
            stage either inline or as a wizard i.e. redirect to a
            seperate page.
        """

        return self.displayMethod or None


    #---------------------------------------------------------------#
    def getDisplayFormat(self):
        """
            Returns how the component will display itself. For this
            particular component. See displayFormat property definition
            for more details.
        """

        return self.displayFormat or None


    #---------------------------------------------------------------#
    def isFilteringEnabled(self):
        """
            Returns BOOLEAN on whether filtering for the component
            is enabled or not
        """
        
        return self.enableFiltering or False
        
    
    #---------------------------------------------------------------#
    def isMultiValued(self):
        """
            Returns if the component is expecting a list ie. can store
            multiple values
        """
        
        return self.multivalued or False


    #---------------------------------------------------------------#
    def _renameEntityTitles(self, entities):
        """
            Iterate through the entities making
        """

        # If an entity title string has been entered in this component's
        # proprties, make sure we rename the displayed title for each
        # entity
        template = self.getEntityTitleTemplate()
        if template:
            if template.startswith('script:'):
                scriptName = template[7:]
                script     = getattr(self.getModel(), scriptName)
                
                # Iterate over each entity executing the given script
                # on each one to get the displayed title
                for entity in entities:

		    # Skip Null entities 
		    if not entity: continue

                    newTitle = script(entity=entity)
                    entity.setTitle(newTitle)
            else:
                # We have to assume that we're expecting entities which
                # correspond to assessments. KERF is useless otherwise in
                # any other context.
                for entity in entities:
                    assessment = entity.getData()
                    if assessment is not None:
                        newTitle = resolveKERF(template, assessment)
                        entity.setTitle(newTitle)

        return entities


    #---------------------------------------------------------------#
    def getNrEntities(self, user = None, filterBy = {}):
        """
            Delegate method. See EntitySource.
        """

        return self.getEntitySource().getNrEntities(user = user, filterBy = filterBy)


    #---------------------------------------------------------------#
    
    def getEntities(self, user = None, filterBy = {}):
        """
            Delegate method. See EntitySource.
        """
        
        es = self.getEntitySource()
        
        if not es: 
            return []
        
        entities = es.getEntities(user = user, filterBy = filterBy)
        entities = self._renameEntityTitles(entities)
        
        if self.getEnableSorting():
            entities.sort( lambda x,y: cmp( x.getTitle(), y.getTitle() ) )

        return entities


    #---------------------------------------------------------------#
    
    def getEntitiesByBatch(self, batchNr, batchSize = 10, user = None, filterBy = {}):
        """
            A wrapper to get the entities from the EntitySource to give
            us the opportunity to possibly modify the entities before
            going out to display.
        """

        es = self.getEntitySource()
        
        if not es: 
            return []

        entities = es.getEntitiesByBatch(batchNr, batchSize = batchSize, user = user, filterBy = filterBy)

        entities = self._renameEntityTitles(entities)
        
        if self.getEnableSorting():
            entities.sort( lambda x,y: cmp( x.getTitle(), y.getTitle() ) )
        
        return entities


    #---------------------------------------------------------------#
    def getEntity(self, entityIdentity, user = None):
        """
            A wrapper for EntitySource getEntity method
        """
        
        es = self.getEntitySource()
        
        if not es:
            return None
        
        entity = es.getEntity(entityIdentity, user = user)
 
        return self._renameEntityTitles([entity]).pop()

    #---------------------------------------------------------------#

    def getComponentState(self, assessmentIdentity, valueType, value, searchIndex=None, searchValue=None, revision=None):
        """
            Return the stored state of the component. This can be
            overridden if the state represents something other than
            a primitive i.e. entities
            
            [Added 20070201 johnm]
        """

        # Iterate over the stored value(s) for the component converting
        # from key to entity
        if value:
            if hasattr(value, 'extend'):
                value = map(lambda e: self.getEntity(e), value)
            else:
                value = self.getEntity(value)
        else:
            value = None

        return AssessmentComponentState(
            assessmentIdentity,
            self.getModel().getIdentity(),
            self.getIdentity(),
            valueType,
            value,
            searchIndex=searchIndex,
            searchValue=searchValue,
            revision=revision
        )


    #---------------------------------------------------------------#
    # BEGIN: Assessible Implementation                              #
    #---------------------------------------------------------------#
    def getAssessmentValueType(self):
        """
            ABSTRACT: Returns the type of the value that is to be stored
            for this component when it's assessment state is persisted.

            NOTE 1: See AssessmentComponentValueTypes for a list of defined contants
            NOTE 2: This method MUST be overriden in the implementing
            class to specify the correct type for value persistence

            RETURN TYPE: String
        """

        #entity chooser assessment values are a string of entity identies chosen
        return st_STRING


    #---------------------------------------------------------------#
    def getDefaultAssessmentState(self, assessment):
        """
            ABSTRACT: Constructs and returns a suitable 'default'
            AssessmentComponentState for the provided Assessment
            instance.

            NOTE: This method MUST be overriden in the implementing
            class to provide a suitable default value for the component

            RETURN TYPE: AssessmentComponentState
        """

        return AssessmentComponentState(
            assessment.getIdentity(),
            assessment.getModelIdentity(),
            self.getIdentity(),
            self.getAssessmentValueType(),
            "",
            revision=assessment.getRevision()
        )


    #---------------------------------------------------------------#
    def updateAssessmentState(self, assessment, formValue, skipMandatory=False):
        """
            ABSTRACT: Updates the specified assessment using
            the provided HTTP REQUEST formValue.

            NOTE: This method MUST be overriden in the implementing
            class to suitably update the component state
        """
        
        Component.updateAssessmentState(self, assessment, formValue, skipMandatory=skipMandatory)

        #get the current assessment state for the component from the assessment
        state = assessment.getComponentState(self.getIdentity())

        # Massage the formValue into a list
        if type(formValue) is not types.ListType:
            value = [formValue]
        else:
            value = formValue
        value.sort()

        previousValues = []
        if state.getValue() is types.ListType:
            previousValues = [entity.getIdentity() for entity in state.getValue()]
        previousValues.sort()

        # Only update this component if the form values differ from current
        # state. We need to do this here due to the nature of Entities.
        if previousValues != value:
            state.setValue( [self.getEntity( item ) for item in value ] )

            # If a searchIndex is defined, add that info to the component state
            searchIndex = self.getSearchIndex()
            if searchIndex:
                state.setSearchIndex(searchIndex)
                state.setSearchValue(value)

            #update the assessment
            assessment.setIsModified(true)
    #---------------------------------------------------------------#
    # END: Assessible Implementation                                #
    #---------------------------------------------------------------#



    #---------------------------------------------------------------#
    # BEGIN: Exportable Implementation                              #
    #---------------------------------------------------------------#
    def getComponentCSVValue(self, assessment, displayReportContent=False):
        """
            Return the CSV friendly version of the component.
        """
        
        value = self.getComponentValue(assessment)
        
        if value:
            if hasattr(value, 'extend'):
                return ",".join([entity.getTitle() for entity in value if entity])
            else:
                return value.getTitle()
        
        return None
    #---------------------------------------------------------------#
    # END: Exportable Implementation                                #
    #---------------------------------------------------------------#



    #---------------------------------------------------------------#
    # BEGIN: Assessment wizard                                      #
    #---------------------------------------------------------------#
    def getAssessmentWizardAndWizardState(self, assessment, wizardName, user=None):
        """
            ABSTRACT: Returns a tuple containing a Wizard instance and
            an associated WizardState instance (Wizard, WizardState)
            for the specified assessment and wizardName.
            
            This method is used to initialise a named assessment wizard
            that will be used during the assessment process on behalf
            of this component.
            
            A value of (None, None) is returned if there is no wizard
            available for the specified wizardName. (or assessment 
            wizards are not supported for the component)
            
            RETURN TYPE: (Wizard, WizardState)
        """ 

        #construct an 'EntityFinder' wizard?
        if wizardName == 'EntityFinder':
            #define the steps
            step1 = WizardStepDefinition("%s Finder" % self.getEntitySource().getEntityNameSingular(), 
                                         "Choose one or more %s from the list of %s below" % (self.getEntitySource().getEntityNamePlural(), self.getEntitySource().getEntityNamePlural()))
            
            # A list of entities to choose from
            entityMappingList = []
            for entity in self.getEntities(user=user, filterBy=self.getFilter(assessment)):
                entityMappingList.append( (self.renderEntity(None, entity=entity).strip(), entity.getIdentity()) )
            
            
            # Determine how the list will displa itself
            step1.addPropertyDefinition(
                PropertyDefinition(
                    identity="entityIdentity",
                    displayName="Available %s:" % self.getEntitySource().getEntityNamePlural(),
                    defaultValue=[],
                    description="",
                    propertyType=pt_NATIVE,
                    propertyStructure=iif(self.isMultiValued(), ps_SET, ps_LIST),
                    propertyFormat=pf_NATIVE,
                    hasEnumeratedValues=true,
                    enumeratedValuesMapping=entityMappingList,
                )
            )
            
            #define the wizard
            wizard = SimpleWizard([step1])
            
            #initialise the wizard state (with initial selections)
            wizardState = WizardState()
            wizardState.setPropertyValue("entityIdentity", assessment.getComponentState(self.getIdentity()).getValue())
            
            #return what we've created so that we can create a wizard with it
            return (wizard, wizardState)
        
        else:
            #unknown wizard requested
            return (None,None)


    #---------------------------------------------------------------#
    def executeAssessmentWizard(self, assessment, wizardName, wizard, wizardState):
        """
            ABSTRACT: Executes the specified assessment wizard with
            the provided wizard state.
            
            Override this method to implement assessment wizard processing
            
            RETURN TYPE: None
        """

        #handle the EnityFinder
        if wizardName == 'EntityFinder':

            #get the existing component state
            state = assessment.getComponentState(self.getIdentity())

            #set the component state to the select entity identity
            state.setValue(wizardState.getPropertyValue('entityIdentity'))


    #---------------------------------------------------------------#
    # END: Assessment wizard                                        #
    #---------------------------------------------------------------#


    #---------------------------------------------------------------#
    # BEGIN: KERLExtensions Implementation                          #
    #---------------------------------------------------------------#
    def compileKERLtoJavaScript(self, expression):
        """
            Returns a Python string that represents a JavaScript
            expression, which may be used to evaluate the status
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.
            
            RETURN TYPE: String         
        """
        
        
        #example: cond="['ec','selected', 'id1']" 
        #returns 'document.getElementById("ec").value=='id1'

        #get the identity (which is the identity of itself)        
        identity = self.getIdentity()

        #determine the operator we are going to process
        opr = expression[1]


        result = "true"

        #an answer is 'selected' if the actual selected answer is the specified answer
        if opr == "selected":
            result = "document.getElementById(\"%s\").value == \"%s\"" % (identity, asJavaScript(expression[2]))
            
        #an answer is 'unselected' if the actual selected answer isn't the specified answer
        elif opr == "unselected":
            result = "document.getElementById(\"%s\").value != \"%s\"" % (identity, asJavaScript(expression[2]))
        
        # An answer is empty if nothing is selected. We just check if there is
        # an element given by the id
        elif opr == "empty":
            result = "document.getElementById(\"%s\") === null" % identity

        # An answer is selected if something is selected
        elif opr == "notempty":
            result = "document.getElementById(\"%s\").value != \"\"" % identity

        return result


    #---------------------------------------------------------------#
    def compileKERLtoEnglish(self, expression):
        """
            Returns a string representing an 'English' version
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            English descriptions of KERL expressions that may be 
            used to describe things like AssessmentFilters
            
            RETURN TYPE: String         
        """
        
        #determine the operator we are going to process
        operator = expression[1]

        result = ""
        
        #the entity is 'selected' 
        if operator == "selected":
            result = "%s '%s' %s is selected" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getEntity(expression[2]).getTitle())
            
        #the entity is 'unselected' 
        elif operator == "unselected":
            result = "%s '%s' %s is not selected" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getEntity(expression[2]).getTitle())
            
        #the entity is 'empty' 
        elif operator == "empty":
            result = "%s '%s' %s is empty" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getEntity(expression[2]).getTitle())
            
        #the entity is 'notempty' 
        elif operator == "notempty":
            result = "%s '%s' %s is not empty" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getEntity(expression[2]).getTitle())

        return result


    #---------------------------------------------------------------#
    def evaluate(self, expression, assessment):
        """
            Evalutes the component class specific KERL expression
            using the provided assessment.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.

            RETURN TYPE: Boolean         
        """
        
        #get the identity (which is the identity of itself)        
        identity = self.getIdentity()

        #get the stored state
        state = assessment.getComponentState(identity)

        #determine the operator we are going to process
        opr = expression[1]

        result = true

        #an answer is 'selected' if the actual selected answer is the specified answer
        if opr == "selected":
            result = state.getValue() == expression[2]
            
        #an answer is 'unselected' if the actual selected answer isn't the specified answer
        elif opr == "unselected":
            result = state.getValue() != expression[2]
        
        # An answer is empty if nothing is selected
        elif opr == "empty":
            result = not not state.getValue()
        
        # An answer is selected if something is selected
        elif opr == "notempty":
            result = not state.getValue()
        
        return result

    
    #---------------------------------------------------------------#
    def getDependencies(self, expression):
        """
            Returns a list of components that are required to
            evaluate the component class specific KERL expression.

            NOTE: Includes the specific component itself.

            RETURN TYPE: {Component} indexed by Component.getIdentity()
       """

        return {self.getIdentity():self}

    
    #---------------------------------------------------------------#
    
    def updateKERLIdentities(self, expression, identityMappings):
        """
            Returns the expression with the appropriate old identities
            (as identified by the identityMappings) replaced with 
            new identities.
        """
        
        #get the component identity from the expression (first element)
        identity = expression[0]
        
        #is the component in the identityMappings?
        if identityMappings.has_key(identity):
            expression[0] = identityMappings[identity]
        
        return expression

    #---------------------------------------------------------------#
    
    def getVocab(self):
        """ Returns a standard Vocab (List of two-tuple key value pairs) of this entity choosers data """
        
        entities = self.getEntities()
        
        results = []
        for entity in entities:
            results.append( (entity.getIdentity(), entity.getTitle()) )
            
        return results
    
    #---------------------------------------------------------------#
    
    def randomize(self, assessment):
        """ Called by the random assessment creator - Picks a random value for this component """
        
        from random import choice        
        
        entities = self.getEntities()
        
        # No options to choose from, so bail.
        if not entities: return
        
        entity = choice(entities)

        self.updateAssessmentState( assessment, entity.getIdentity() )
        
    #---------------------------------------------------------------#
    
    
    

    #---------------------------------------------------------------#
    # END: KERLExtensions Implementation                            #
    #---------------------------------------------------------------#


    #-------------------------------------------------------------------------
    # BEGIN: AJAXy stuff
    #-------------------------------------------------------------------------
    def numericSort(self, enumerable, attributeName=None):
        """
            Numerically sorts an list of strings or alternatively, a list of
            objects where the given attributeName identifies which field to sort
            on.
        """
        
        def _generate_index(str):
            """
            Splits a string into alpha and numeric elements, which
            is used as an index for sorting"
            """

            index = []
            def _append(fragment, temp=index):
                if fragment.isdigit():
                    fragment = int(fragment)
                temp.append(fragment)

            # initialize loop
            prev_isdigit = str[0].isdigit()
            current_fragment = ''
            for char in str:
                curr_isdigit = char.isdigit()
                if curr_isdigit == prev_isdigit:
                    current_fragment += char
                else:
                    _append(current_fragment)
                    current_fragment = char
                    prev_isdigit = curr_isdigit

            _append(current_fragment)    

            return tuple(index)

        if attributeName:
            indices = map(_generate_index, map(lambda x: getattr(x, attributeName)(), enumerable))
        else:
            indices = map(_generate_index, enumerable)
            
        decorated = zip(indices, enumerable)
        decorated.sort()

        return [item for index, item in decorated]


    #-------------------------------------------------------------------------
    def ajax_performSearch(self, REQUEST=None, **args):
        """
            Filters the list of entities based on the search query
            
            TODO: Need to move the search capabilities to the specific
                  datasource rather than using python (johnm)
        """

        # We need a user. Let's grab it.
        userService = getUserProviderService(self)
        user = REQUEST['AUTHENTICATED_USER']
        keUser = userService.getUser(user)
        
        # Make sure we have the required params
        searchQuery = REQUEST.form.get('queryString', '')
        componentIdentity = REQUEST.form.get('componentIdentity', None)
        
        if not componentIdentity:
            return

        # Get all our entities
        entities = self.getEntities( user = keUser, filterBy = { 'authenticatedUser' : '%s' % user })

        # Filter titles against the search query.
        # 
        # NOTE: Unfortunately, this must happen *after* the DB query as the
        # title may be modified by KERL
        searchQuery = searchQuery.lower()
        entities = filter(lambda e: searchQuery in e.getTitle().lower(), entities)
        
        if len(entities) > 25:
            entities = entities[:25]

        # Sort the resulting entity set
        #
        # TODO: Extend to do a numeric sort?
        #
        # entities.sort(lambda x,y: cmp(x.getTitle(), y.getTitle()))
        entities = self.numericSort(entities, 'getTitle')


        # Build the JSON response containing the entity details
        result = []
        if entities:
            for entity in entities:
                jsonEntity = '{"identity":"%s","title":"%s"}' % (entity.getIdentity(), entity.getTitle())
                result.append(jsonEntity);
        jsonResponse = "[%s]" % ','.join(result)
        
        REQUEST.RESPONSE.setHeader('X-JSON', jsonResponse)
        
        return "A message or three"
    #-------------------------------------------------------------------------
    # END: AJAXy stuff
    #-------------------------------------------------------------------------


# ======================================================================
# Register Component Information with the Knowledge Engine
# ======================================================================
registerGenericObjectClass(EntityChooser)
